package q.yazg;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.*;
import javax.swing.JFrame;
import org.json.JSONException;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;

import static org.lwjgl.opengl.GL11.*;
import static q.yazg.Utils.*;
import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.opengl.TextureLoader;

/**
 *
 * @author Kamil
 */
public final class Main {
    
    public static final int DISPLAY_WIDTH  = Constants.getInt("display.width" , 800);
    public static final int DISPLAY_HEIGHT = Constants.getInt("display.height", 600);
    
    private static float dx,dy;
    
    private static final float CAMERA_A = Constants.getFloat("display.camera.a", 2.0f);
    private static final float CAMERA_B = Constants.getFloat("display.camera.b", 2.0f);
    private static final float CAMERA_UPDATE_TIME = Constants.getFloat("display.camera.response-time", 2.0f)*1000f;
    private static float cx;
    private static Easing easing;
    
    private static final float STEP_X  = Constants.getFloat("mouse.x-treshold", 0),
                               STEP_Y  = Constants.getFloat("mouse.y-treshold", 0);
    private static final float DECAY_X = Constants.getFloat("mouse.x-decay"   , 0),
                               DECAY_Y = Constants.getFloat("mouse.y-decay"   , 0);
    
    public  static final float PPM     = Constants.getFloat("display.pixelsPerMeter", 1f);    
    public  static final float G       = Constants.getFloat("gravity", 10f)/(1000*1000);
    
    public  static final boolean DEBUG_WINDOW_VISIBLE = Constants.getBoolean("debug.debugWindow", false); 
    public  static final JFrame  DEBUG_WINDOW         = DEBUG_WINDOW_VISIBLE?new JFrame("debug"):null;
    
    private static List<Entity>     entities;
    private static List<Background> backgrounds;
    private static Terrain          terrain;
    private static Followable       followed;
    
    public static MessageThread msgThread;
               
    private Main(){}
    
    public static void main(String[] args) throws IOException, JSONException, LWJGLException{        
        initGL();
        init();
        if(DEBUG_WINDOW_VISIBLE){
            DEBUG_WINDOW.setVisible(true);
            DEBUG_WINDOW.pack();
        }
        while(!Display.isCloseRequested())loop();
        Mouse.setGrabbed(false);
        Display.destroy();
        if(DEBUG_WINDOW_VISIBLE){
            DEBUG_WINDOW.setVisible(false);
        }
        msgThread.shut();
        System.exit(0);
    }
    
    private static void initGL(){
        try {
            Display.setDisplayMode(new DisplayMode(DISPLAY_WIDTH, DISPLAY_HEIGHT));
            Display.setTitle(
                        Constants.getString("display.title","Yet Another Zombie Game")
                    );
            Display.create();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(0);
        }
        glEnable(GL_TEXTURE_2D);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(-DISPLAY_WIDTH/2, DISPLAY_WIDTH/2, -DISPLAY_HEIGHT/2, DISPLAY_HEIGHT/2, 1, -1);
        glMatrixMode(GL_MODELVIEW);
        Mouse.setGrabbed(true);
    }
    
    private static void init() throws IOException, JSONException, LWJGLException {
        
        entities = new ArrayList<>();
        backgrounds = new ArrayList<>();
                
        msgThread = new MessageThread();
        msgThread.start();
        
        msgThread.addListener(FollowMessage.class, new Listener() {

            @Override
            public void message(Message msg) {
                FollowMessage fmsg = (FollowMessage) msg;
                if(fmsg.isSet()){
                    followed=fmsg.getFollowable();
                } else {
                    easing.reInit(cx, (followed.getVelocity()*CAMERA_B+CAMERA_A)*followed.getDirection(), CAMERA_UPDATE_TIME);
                }
            }
        });
        
        Mouse.create();
        
        entities.add(new PlayerEntity());
        terrain = new SimpleTerrain();
        backgrounds.add(new Background(0, TextureLoader.getTexture("PNG", new FileInputStream("bg.png"))));
        Collections.sort(backgrounds);
        
        easing = new Easing(0, 0, 0);
        cx = followed.getDirection()*CAMERA_A;
        
        getTicks();
        
        while(Mouse.next()){
            dx += Mouse.getDX();
            dy += Mouse.getDY();
        }
    }
    
    private static void loop(){
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);        
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        
        int ticks = getTicks();
        while(Mouse.next()){
            if(Mouse.getEventButton()!=1 && Mouse.getEventButtonState()){
                msgThread.sendMessage(MouseMessage.lClick);
            }
            dx += Mouse.getDX();
            if(dx>STEP_X){
                dx=0;
                msgThread.sendMessage(MouseMessage.moveRight);
            } else if(dx<-STEP_X){
                dx=0;
                msgThread.sendMessage(MouseMessage.moveLeft);
            }
            dy += Mouse.getDY();
            if(dy>STEP_Y){
                dy=0;
                msgThread.sendMessage(MouseMessage.moveUp);
            } else if(dy<-STEP_Y){
                dy=0;
                msgThread.sendMessage(MouseMessage.moveDown);
            }
            dx*=Math.pow(DECAY_X,ticks);
            dy*=Math.pow(DECAY_Y,ticks);
        }
        
        for(Background bg:backgrounds)bg.render();
        
        if(!easing.isDone()){
            cx=easing.update(ticks);
        }
        
        glScalef(PPM, PPM, 1f);
        glTranslatef(-cx, 0f, 0f);
        glTranslatef(-followed.getX(),0f,0f);
        
        terrain.render(cx-(DISPLAY_WIDTH/2)/PPM,cx+(DISPLAY_WIDTH/2)/PPM);
        
        for(Entity e:entities)e.update(ticks);
        for(Entity e:entities)e.render();
        
        Display.update();
    }
    
    private static long lastTime;
    /**
     * returns miliseconds since last call of this method
     * @return miliseconds since last call
     */
    private static int getTicks() {
        if(lastTime==0){
            lastTime=Sys.getTime();
            return 1;
        }
        long ret =Sys.getTime() - lastTime;
        ret=(ret*1000)/Sys.getTimerResolution();
        lastTime=Sys.getTime();
        return (int)ret;
    }
    
    @Deprecated
    static float collideWithTerrain(Entity entity, float prevY) {
        final float w = entity.getW()/2,
                    h = entity.getH()/2,
                    x = entity.getX(),
                    y = entity.getY();
        float baseY = terrain.getHeight(x-w, x+w, y-h)+h;        
        if(baseY>(y-h)){
            if(prevY<baseY){
                entity.gowbeck();
                return prevY;
            } else {
                entity.setOnGround();
                return baseY;
            }
        } else {
            return y;
        }
    }
    
    public static float collideWithTerrainX(Entity e,float prevX){
        float baseY = e.getY()-e.getH()/2;
        if(terrain.getHeight(e.getX()-e.getW()/2, e.getX()+e.getW()/2, baseY)>baseY){
            return prevX;
        } else {
            return e.getX();
        }
    }
    
    public static boolean checkOnGround(Entity e){        
        float baseY = e.getY()-e.getH()/2;
        return baseY == terrain.getHeight(e.getX()-e.getW()/2, e.getX()+e.getW()/2, baseY);
    }
    
    public static float collideWithTerrainY(Entity e){
        float baseY = e.getY()-e.getH()/2;
        float terrainY = terrain.getHeight(e.getX()-e.getW()/2, e.getX()+e.getW()/2, baseY);
        return max(baseY,terrainY)+e.getH()/2;
    }
    
    public static void renderQuad(float x, float y, float w, float h, float texX, float texY, float texW, float texH, Texture tex){
        final float hw = w/2, hh = h/2;
        tex.bind();
        glBegin(GL_QUADS);
        glTexCoord2f(texX       , texY + texH); glVertex2f(x - hw, y - hh);
        glTexCoord2f(texX + texW, texY + texH); glVertex2f(x + hw, y - hh);
        glTexCoord2f(texX + texW, texY       ); glVertex2f(x + hw, y + hh);
        glTexCoord2f(texX       , texY       ); glVertex2f(x - hw, y + hh);
        glEnd();
    }
    
    public static void renderFlipQuad(float x, float y, float w, float h, float texX, float texY, float texW, float texH, Texture tex){
        final float hw = w/2, hh = h/2;
        tex.bind();
        glBegin(GL_QUADS);
        glTexCoord2f(texX + texW, texY + texH); glVertex2f(x - hw, y - hh);
        glTexCoord2f(texX       , texY + texH); glVertex2f(x + hw, y - hh);
        glTexCoord2f(texX       , texY       ); glVertex2f(x + hw, y + hh);
        glTexCoord2f(texX + texW, texY       ); glVertex2f(x - hw, y + hh);
        glEnd();
    }
    
    public static void renderQuad(float x, float y, Texture tex){        
        final float hw = tex.getImageWidth()/2, hh = tex.getImageHeight()/2;
        final float tw = tex.getWidth(), th = tex.getHeight();        
        tex.bind();
        glBegin(GL_QUADS);
        glTexCoord2f(0  , th); glVertex2f(x - hw, y - hh);
        glTexCoord2f(tw , th); glVertex2f(x + hw, y - hh);
        glTexCoord2f(tw , 0 ); glVertex2f(x + hw, y + hh);
        glTexCoord2f(0  , 0 ); glVertex2f(x - hw, y + hh);
        glEnd();
    }
    
    public static void renderEntity(float x, float y, float w, float h, float texX, float texY, float texW, float texH, Texture tex, boolean flip){        
        if(flip)renderFlipQuad(x,y,w/PPM,h/PPM,texX,texY,texW,texH,tex);
        else    renderQuad    (x,y,w/PPM,h/PPM,texX,texY,texW,texH,tex);
    }
    
  
}
